<template>
  <div class="chat-section">
    <div class="chat-remind-container" v-if="chatType == 0">
      <p>请注意：您当前正以访客模式咨询,为保证信息安全， 5条消息之后将无法咨询请先登录或注册</p>
      <div class="action-container">
        <a class="account-action" href="/account/sign-in">登录</a>
        <a class="account-action" href="/account/sign-up">注册</a>
      </div>
    </div>
    <vue-advanced-chat
      class="chat-panel"
      single-room="true"
      show-search="false"
      show-audio="false"
      show-add-room="false"
      room-info-enabled="true"
      accepted-files="image/png, image/jpeg"
      :height="screenHeight"
      :current-user-id="senderId"
      :rooms="JSON.stringify(rooms)"
      :rooms-loaded="true"
      :messages="JSON.stringify(messages)"
      :messages-loaded="messagesLoaded"
      @send-message="sendMessage($event.detail[0])"
      @fetch-messages="fetchMessages($event.detail[0])"
      @open-file="openFile($event.detail[0])"
    />
    <a-modal
      v-model="visible"
      :closable="true"
      title="温馨提示"
      ok-text="登录"
      cancel-text="注册"
      @cancel="handleClose"
    >
      <template slot="footer">
        <a-button @click="handleCancel"> 注册 </a-button>
        <a-button type="primary" @click="handleOk"> 登录 </a-button>
      </template>
      <p>请注意：您当前正以访客模式咨询,为保证信息安全， 5条消息之后将无法咨询请先登录或注册</p>
    </a-modal>
  </div>
</template>

<script>
import Vue from 'vue'
import { GUEST_ACCESS_TOKEN, ACCESS_TOKEN, GUEST_ID, GUEST_ID_EXPIRES_IN } from '@/store/mutation-types'

import { encrypt, decrypt } from '@/utils/encryption/crypto-util'
import createWebSocket from '@/utils/WebSocketClient'
import { randomUUID } from '@/utils/util'
import { register } from 'vue-advanced-chat'
register()
import { mapState, mapActions } from 'vuex'
import Fingerprint2 from 'fingerprintjs2'

//心跳检测时间10s
const PING_INTERVAL = 10 * 1000
//重连间隔0.5s
const RECONNECT_INTERVAL = 0.5 * 1000

export default {
  props: {
    value: {
      type: Boolean,
      default: false,
    },
  },

  model: {
    prop: 'value',
    event: 'input',
  },

  data() {
    return {
      // 0-访客模式 1-用户模式
      chatType: 0,
      shopId: undefined,

      rooms: [],
      messages: [],
      messagesLoaded: false,

      ws: null,

      visible: false,
    }
  },

  computed: {
    ...mapState('user', {
      userInfo: (state) => state.info,
    }),

    ...mapState('guest', {
      guestInfo: (state) => state.info,
    }),

    ...mapState('customerSupport', {
      shopSupportInfo: (state) => state.shopSupportInfo,
      platformSupportInfo: (state) => state.platformSupportInfo,
    }),

    ...mapState('iMConversation', {
      conversationInfo: (state) => state.info,
    }),

    ...mapState('iMMessage', {
      items: (state) => state.items,
      pageSize: (state) => state.pageSize,
      pageTotal: (state) => state.pageTotal,
      totalCount: (state) => state.totalCount,
      currentPage: (state) => state.currentPage,
      originalPage: (state) => state.originalPage,
    }),

    screenHeight() {
      if (this.chatType == 0) {
        return 'calc(55vh + 90px)'
      } else {
        return 'calc(55vh)'
      }
    },

    isGuestLimited() {
      if (this.chatType == 0) {
        const results = this.messages.filter((item) => item.senderId == this.senderId)
        if (results && results.length > 4) {
          return true
        }
      }

      return false
    },

    senderId() {
      if (this.chatType == 1) {
        return this.userInfo ? this.userInfo.id : undefined
      } else {
        return this.guestInfo ? this.guestInfo.id : undefined
      }
    },

    receiverId() {
      if (this.chatType == 1) {
        // 目前默认官方客服
        // return this.shopSupportInfo.sysUserId
        return this.platformSupportInfo.sysUserId
      } else {
        return this.platformSupportInfo.sysUserId
      }
    },
  },

  watch: {
    visible: {
      immediate: true,
      handler(newValue, oldValue) {
        console.log('\n\n visible update:' + JSON.stringify(this.visible) + '\n\n')
        // this.$emit('update:value', val)
        this.$emit('input', newValue)
      },
    },
  },

  created() {
    console.log('\n\n' + 'created' + '\n\n')
    // const that = this
    // const query = this.$route.query
    // this.chatType = query.chatType
    // this.shopId = query.shopId

    // // 1 根据chatType获取senderID 和 receiverID 0-访客咨询 1-商品咨询
    // this.getSenderIdAndReceiverId()
    //   .then(() => {
    //     // 2 根据senderID 和 receiverID获取会话信息
    //     that
    //       .getConversation()
    //       .then(() => {
    //         // 3 基于会话信息创建WebSocket通信
    //         that.connectWebSocket()
    //         // 4 基于WebSocket通信处理消息业务
    //       })
    //       .catch(() => {
    //         that.$message.error('获取会话信息失败')
    //       })
    //   })
    //   .catch((error) => {
    //     that.$message.error('获取客服信息失败')
    //   })
  },

  destroyed: function () {
    if (this.ws) {
      this.ws.close()
    }
  },

  methods: {
    show(query) {
      const that = this
      console.log('\n\n' + 'show' + '\n\n')
      this.chatType = query.chatType
      this.shopId = query.shopId

      // 1 根据chatType获取senderID 和 receiverID 0-访客咨询 1-商品咨询
      this.getSenderIdAndReceiverId()
        .then(() => {
          // 2 根据senderID 和 receiverID获取会话信息
          that
            .getConversation()
            .then(() => {
              // 3 基于会话信息创建WebSocket通信
              that.connectWebSocket()
              // 4 基于WebSocket通信处理消息业务
            })
            .catch(() => {
              that.$message.error('获取会话信息失败')
            })
        })
        .catch((error) => {
          that.$message.error('获取客服信息失败')
        })
    },

    validateGuestLimit() {
      const that = this
      if (this.isGuestLimited) {
        this.visible = true
      }
    },

    getGuestInfoById(guestId) {
      const that = this
      return new Promise((resolve, reject) => {
        if (guestId) {
          that.$ls.set(GUEST_ID, guestId, GUEST_ID_EXPIRES_IN)
          const parameters = { id: guestId }
          this.$store
            .dispatch('guest/getGuestAction', parameters)
            .then(() => {
              const parameters = {
                username: that.guestInfo.username,
              }
              // 访客登录以获得token通过ws的鉴权
              that.$store
                .dispatch('user/guestSignInAction', parameters)
                .then(() => {
                  resolve()
                })
                .catch((error) => {
                  reject()
                  that.$message.error(error.message)
                })
            })
            .catch((error) => {
              reject()
              that.$message.error(error.message)
            })
        } else {
          reject()
          that.$message.error('构建访客ID失败')
        }
      })
    },

    getGuestInfo() {
      return new Promise((resolve, reject) => {
        const that = this
        var guestId = this.$ls.get('GUEST_ID')
        if (!guestId) {
          // 通过浏览器指纹信息生成访客ID
          Fingerprint2.get((components) => {
            guestId = Fingerprint2.x64hash128(components.map((component) => component.value).join(''), 31)
            console.log('\n\n Fingerprint2 id :' + JSON.stringify(guestId) + '\n\n')
            // 如果通过浏览器指纹信息生成访客ID失败，就用随机串代替
            if (!guestId) guestId = randomUUID()
            that
              .getGuestInfoById(guestId)
              .then(() => resolve())
              .catch(() => reject())
          })
        } else {
          this.getGuestInfoById(guestId)
            .then(() => resolve())
            .catch(() => reject())
        }
      })
    },

    getUserInfo() {
      return new Promise((resolve, reject) => {
        const that = this
        const token = Vue.ls.get(ACCESS_TOKEN)
        if (token) {
          this.$store
            .dispatch('user/getUserInfoAction')
            .then(() => {
              resolve()
            })
            .catch((error) => {
              reject()
            })
        } else {
          that.$message.error('授权失效请重新登陆')
          that.$router.push({
            path: '/',
          })
        }
      })
    },

    getSenderInfo(chatType) {
      const that = this
      return new Promise((resolve, reject) => {
        // 获取当前登录用户信息
        if (chatType == 1) {
          that
            .getUserInfo()
            .then(() => {
              resolve()
            })
            .catch((error) => {
              reject(error)
            })
        } else {
          // 获取访客信息
          that
            .getGuestInfo()
            .then(() => {
              resolve()
            })
            .catch((error) => {
              reject(error)
            })
        }
      })
    },

    getShopSupportInfo() {
      const that = this
      return new Promise((resolve, reject) => {
        const parameters = {
          shopId: that.shopId,
        }
        this.$store
          .dispatch('customerSupport/getShopSupportInfoAction', parameters)
          .then(() => resolve())
          .catch((error) => reject(error))
      })
    },

    getPlatformSupportInfo() {
      return new Promise((resolve, reject) => {
        this.$store
          .dispatch('customerSupport/getPlatformSupportInfoAction')
          .then(() => resolve())
          .catch(() => reject())
      })
    },

    getReceiverInfo(chatType) {
      return new Promise((resolve, reject) => {
        // 目前默认官方客服
        this.getPlatformSupportInfo()
          .then(() => resolve())
          .catch((error) => reject(error))

        //访客模式或者店铺ID为空连接到官方客服
        // if (chatType == 0 || !this.shopId) {
        //   this.getPlatformSupportInfo()
        //     .then(() => resolve())
        //     .catch((error) => reject(error))
        // } else {
        //   this.getShopSupportInfo()
        //     .then(() => resolve())
        //     .catch((error) => reject(error))
        // }
      })
    },

    getSenderIdAndReceiverId() {
      const that = this
      const chatType = this.chatType
      return new Promise((resolve, reject) => {
        that
          .getSenderInfo(chatType)
          .then(() => {
            that
              .getReceiverInfo(chatType)
              .then(() => resolve())
              .catch((error) => reject(error))
          })
          .catch((error) => reject(error))
      })
    },

    getSocketUrl() {
      // const url =
      //   window._CONFIG['domianURL'].replace('https://', 'wss://').replace('http://', 'ws://') +
      //   '/websocket/' +
      //   this.senderId
      // return url.replace('api//', 'api/')

      const url =
       "https://chat.htxincailiao.com:5532".replace('https://', 'wss://').replace('http://', 'ws://') +
        '/websocket/' +
        this.senderId
      return url.replace('api//', 'api/')
    },

    getWsOptions() {
      var token = null
      if (this.chatType == 0) {
        token = Vue.ls.get(GUEST_ACCESS_TOKEN)
      } else if (this.chatType == 1) {
        token = Vue.ls.get(ACCESS_TOKEN)
      }

      return [token]
    },

    connectWebSocket() {
      const url = this.getSocketUrl()
      // console.log('\n\n connectWebSocket url = ' + url + '\n\n');
      const wsOptions = this.getWsOptions()
      this.ws = createWebSocket(url, {
        wsOptions: wsOptions,
        autoReconnect: true,
        pingInterval: PING_INTERVAL,
        reconnectInterval: RECONNECT_INTERVAL,
      })

      this.ws.on('open', (event) => {
        console.log('WebSocket opened', event)
      })

      this.ws.on('message', (event) => {
        // console.log('WebSocket message received:', event.data)
        var data = eval('(' + event.data + ')') //解析对象
        if (data.cmd == 'chat') {
          this.receiveMessage(data)
        }
      })

      this.ws.on('close', (event) => {
        console.log('WebSocket closed', event)
      })

      this.ws.on('error', (event) => {
        console.error('WebSocket error:', event)
      })
    },

    getConversation() {
      const that = this
      return new Promise((resolve, reject) => {
        const parameters = {
          ownerId: this.senderId,
          targetId: this.receiverId,
        }

        this.$store
          .dispatch('iMConversation/getConversationAction', parameters)
          .then(() => {
            const conversation = that.conversationInfo
            that.rooms.push({
              roomId: conversation.id,
              roomName: conversation.targetNickname,
              avatar: that.$fileBaseURL + conversation.targetAvatar,
              users: [
                { _id: conversation.ownerId, username: conversation.ownerNickname },
                { _id: conversation.targetId, username: conversation.targetNickname },
              ],
            })
            resolve()
          })
          .catch((error) => {
            reject(error)
          })
      })
    },

    resetMessages() {
      this.messagesLoaded = false
      this.messages = []
    },

    fetchMessages({ room, options = {} }) {
      if (options.reset) {
        this.resetMessages()
      }

      const conversation = this.conversationInfo
      if (conversation) {
        const isRefresh = this.items.length < this.totalCount ? false : true
        this.updateList(conversation, isRefresh)
      }
    },

    updateList(conversation, isRefresh) {
      const that = this
      const parameters = {
        pageSize: this.pageSize,
        pageNo: this.currentPage,
        conversationId: conversation.id,
        chatType: this.chatType,
      }

      const autoIncrementPage = true
      this.$store
        .dispatch('iMMessage/getListAction', { isRefresh, parameters, autoIncrementPage })
        .then(() => {
          const messages = JSON.parse(JSON.stringify(that.messages))

          for (var i = that.messages.length; i < that.items.length; i++) {
            const parsedMessage = that.parseMessage(that.items[i], conversation)
            messages.unshift(parsedMessage)
          }

          that.messages = messages
          that.validateGuestLimit()

          if (messages.length === 0 || messages.length < this.pageSize || this.messages.length >= this.totalCount) {
            setTimeout(() => {
              this.messagesLoaded = true
            }, 0)
          } else {
            this.messagesLoaded = false
          }
        })
        .catch((error) => {
          that.$message.error(error.message)
        })
    },

    formattedFiles(files) {
      const formattedFiles = []

      files.forEach((file) => {
        const messageFile = {
          name: file.name,
          size: file.size,
          type: file.type,
          extension: file.extension || file.type,
          url: file.url || file.localUrl,
        }

        if (file.audio) {
          messageFile.audio = true
          messageFile.duration = file.duration
        }

        formattedFiles.push(messageFile)
      })

      return formattedFiles
    },

    openFile({ file }) {
      window.open(file.file.url, '_blank')
    },

    async sendMessage({ content, roomId, files, replyMessage }) {
      this.validateGuestLimit()
      if (this.isGuestLimited) {
        return
      }

      const conversation = this.conversationInfo
      await this.sendConversationMessage(conversation.ownerId, conversation.targetId, conversation, {
        content,
        roomId,
        files,
        replyMessage,
      })
    },

    async sendConversationMessage(senderId, receiverId, conversation, { content, roomId, files, replyMessage }) {
      const id = this.messages.length
      // 0 - 文本 1 - 图片
      var type = 0
      const message = {
        _id: id,
        senderId: senderId,
        content,
        timestamp: new Date().toString().substring(16, 21),
        date: new Date().toDateString(),
      }

      this.messages = [...this.messages, message]

      if (files) {
        message.files = this.formattedFiles(files)
        for (let index = 0; index < files.length; index++) {
          await this.uploadFile({ file: files[index], message: message })
        }
      }

      var c = message.content
      if (files) {
        type = 1
        c = JSON.stringify(files)
      }

      // 发送消息到服务器
      const key = conversation.cryptoKey
      const iv = conversation.cryptoIv
      const body = {
        cmd: 'chat',
        type: type,
        conversationId: conversation.id,
        receiverId: receiverId,
        senderId: senderId,
        content: encrypt(c, key, iv),
        timestamp: new Date().getTime(),
      }

      this.ws.send(JSON.stringify(body))
    },

    async uploadFile({ file, message }) {
      const that = this
      return new Promise((resolve, reject) => {
        let type = file.extension || file.type
        if (type === 'svg' || type === 'pdf') {
          type = file.type
        }

        const onUploadProgress = (progressEvent) => {
          const progress = Math.round((progressEvent.loaded * 100) / progressEvent.total)
          that.updateFileProgress(message._id, file.localUrl, progress)
        }

        const blob = file.blob
        const name = file.name + '.' + file.extension

        let formData = new FormData()
        formData.append('file', blob, name)
        const parameters = { formData, onUploadProgress }

        this.$store
          .dispatch('upload/commonAction', parameters)
          .then((url) => {
            that.$message.success('上传成功')
            message.files.forEach((f) => {
              if (f.url === file.localUrl) {
                f.url = that.$fileBaseURL + url
                f.progress = -1
              }
            })

            //更新file的url为服务器相对路径 用于服务器保存
            file.url = url
            this.messages = [...this.messages]
            resolve()
          })
          .catch(() => {
            that.$message.error('上传失败')
            reject()
          })
          .finally(() => {
            that.resetUpload()
          })
      })
    },

    updateFileProgress(messageId, fileUrl, progress) {
      const message = this.messages.find((message) => message._id === messageId)

      if (!message || !message.files) return

      message.files.find((file) => file.url === fileUrl).progress = progress
      this.messages = [...this.messages]
    },

    resetUpload() {},

    parseMessage(message, conversation) {
      const key = conversation.cryptoKey
      const iv = conversation.cryptoIv
      const type = message.type
      var content = message.content
      if (type == 1 && content && content.length > 0) {
        const filesContent = decrypt(content, key, iv)
        // console.log('\n\n filesContent:' + JSON.stringify(filesContent) + '\n\n')
        message.files = JSON.parse(filesContent)
        message.files.forEach((f) => {
          f.url = this.$fileBaseURL + f.url
        })
        message.content = ''
      } else {
        message.content = decrypt(content, key, iv)
      }

      message._id = message.id ? message.id : this.messages.length
      message.timestamp = new Date(Number(message.timestamp)).toLocaleString()

      return message
    },

    receiveMessage(message) {
      this.messages = [...this.messages, this.parseMessage(message, this.conversationInfo)]
    },

    handleCancel() {
      this.visible = false
      this.$parent.$emit('handleCancel')
      this.$router.push({
        path: '/account/sign-up',
      })
    },

    handleOk() {
      this.visible = false
      this.$router.push({
        path: '/account/sign-in',
      })
    },

    handleClose() {
      this.visible = false
    },
  },
}
</script>

<style lang="less">
.chat-section {
  // height: 50vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.chat-remind-container {
  min-width: 800px;
  color: red;
  padding: var(--padding-small) var(--padding-small);
  margin: 0 var(--margin-small);
}

.chat-panel {
  // min-width: 300px;
  min-width: 800px;
  height: 300px;
}
.action-container {
  width: 80px;
  display: flex;
  justify-content: space-between;
  // margin-top: var(--margin-small);
}

.account-action {
  color: var(--r-primary-color);
  padding: var(--padding-xx-small) 0 0 0;
  // margin-bottom: var(--margin-small);
  // height: 32px;
}
</style>
